<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Coder-nav</title>
    <script src="/js/jquery/jquery.js"></script>
    <script src="/js/jquery/adsorb.js"></script>
    <script src="/js/vue/vue.js"></script>
    <script src="/js/vue/axios.js"></script>
    <script src="/js/vue/element-ui.js"></script>
    <script src="/js/echarts/echarts.js"></script>
    <script src="/js/echarts/westeros.js"></script>
    <script src="/js/codemirror/codemirror.js"></script>
    <script src="/js/codemirror/show-hint.js"></script>
    <script src="/js/codemirror/clike.js"></script>
    <script src="/js/codemirror/mode/javascript.js"></script>
    <script src="/js/util.js"></script>
    <script src="/js/vditor/vditor.js"></script>
    <style>
        @import url("/css/vue/element-ui.css");
        @import url("/css/codemirror/codemirror.css");
        @import url("/css/codemirror/show-hint.css");
        @import url("/css/codemirror/theme/material.css");
        @import url("/css/codemirror/theme/monokai.css");
        @import url("/css/codemirror/theme/darcula.css");
        @import url("/css/codemirror/theme/idea.css");
        @import url("/css/codemirror/theme/mdn-like.css");
        @import url("/css/codemirror/theme/duotone-light.css");
        @import url("/css/vditor/vditor.css");
    </style>
    <style>
        .main {
            position: absolute;
            width: 70%;
            height: 1px;
            top: 50px;
        }
        .el-table__body-wrapper::-webkit-scrollbar {
            display: none;
        }
        .el-textarea__inner {
            background-color: whitesmoke;
        }
        .CodeMirror {
            font-family: "Consolas", monospace;
            line-height: 1.8;
            height: 450px;
            padding: 10px;
            margin-top: 5px;
            margin-bottom: 5px;
            border-radius: 10px;
        }
        #code::-webkit-scrollbar {
            display: none;
        }
        h4 {
            color: darkslategray;
        }
        .custom-tree-node {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: space-between;
            font-size: 14px;
            overflow: hidden; /* 确保内容不会溢出容器 */
        }
        .el-tree {
            background: rgba(0, 0, 0, 0);
        .el-checkbox__inner {
            background: rgba(0, 0, 0, 0);
        }
        .el-tree-node__content:hover {
            background: rgba(0, 0, 0, 0);
        }
        .el-tree-node:focus > .el-tree-node__content {
            background-color: rgba(0, 0, 0, 0);
        }
        }
        /* 提示框整体样式 */
        .CodeMirror-hints {
            position: absolute;
            z-index: 10;
            font-size: 18px;
            font-weight: bold;
            overflow-y: auto !important;
        }
        .CodeMirror-hint {
            padding-right: 10px;
            color: darkslategrey;
        }
        /* 当前选中项高亮 */
        .CodeMirror-hint-active {
            background: lightskyblue !important;
            color: white !important;
        }
        /* 滚动条美化 */
        .CodeMirror-hints::-webkit-scrollbar {
            width: 6px;
        }
        .CodeMirror-hints::-webkit-scrollbar-track {
            background: lightgray;
        }
        .CodeMirror-hints::-webkit-scrollbar-thumb {
            background: lightgray;
            border-radius: 3px;
        }
        .el-aside-tree {
            width: 20vw;
            height: 90vh;
            margin-top: 50px;
            padding-left: 15px;
            font-size: 16px;
            overflow-y: auto;
        }
        .el-aside-tree::-webkit-scrollbar {
            display: none;
        }
    </style>
    <style>
        [data-scope="hide-border"] .el-input__inner {
            border: none !important;
            padding-left: 8px;
            outline: none !important;
        }
        [data-scope="custom-tree"] .el-tree-node {
            margin: 5px 0;
        }
        [data-scope="custom-tree"] .el-tree-node span {
            font-size: 16px;
        }
        [data-scope="custom-tree"] .el-tree-node__content {
            margin: 3px;
            height: 28px;
            font-size: 16px;
            font-family: Consolas;
        }
        [data-scope="custom-tree"] .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
            background-color: #efefef !important;
        }
        [data-scope="custom-tree"] .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content input {
            background-color: #efefef !important;
        }
        /* 右键菜单样式 */
        .context-menu {
            position: fixed;
            z-index: 1000;
            background: #fff;
            border: 1px solid #ddd;
            border-radius: 4px;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
            padding: 5px 0;
            min-width: 150px;
        }
        .context-menu-item {
            padding: 8px 15px;
            cursor: pointer;
            font-size: 14px;
            display: flex;
            align-items: center;
        }
        .context-menu-item:hover {
            background-color: #f5f5f5;
        }
        .context-menu-item i {
            margin-right: 8px;
        }
        /* 新增样式：树节点文本溢出处理 */
        .tree-node-text {
            flex: 1;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            max-width: calc(100% - 10px); /* 为图标和操作按钮留出空间 */
        }
        /* 自定义tooltip样式 */
        .tree-node-tooltip {
            max-width: 300px;
            word-break: break-all;
        }
    </style>
</head>
<body>
<div id="app">
    <el-page-header @back="goBack" style="position: fixed;z-index: 999;">
        <template #content>
            <el-select ref="selectRef" v-model="selectedState" size="small" style="width: 155px">
                <el-option label="🍡&emsp;导航主页" value="0"><el-link href="/">🍡&emsp;导航主页</el-link></el-option>
                <el-option label="📌&emsp;记事本" value="1"><el-link href="/memo">📌&emsp;记事本</el-link></el-option>
                <el-option label="💰&emsp;记账本" value="2"><el-link href="/cash">💰&emsp;记账本</el-link></el-option>
                <el-option label="🌈&emsp;动态页面" value="3"><el-link href="/page">🌈&emsp;动态页面</el-link></el-option>
                <el-option label="⏱️&emsp;定时任务" value="4"><el-link href="/task">⏱️&emsp;定时任务</el-link></el-option>
                <el-option label="⚙️&emsp;个人设置" value="5"><el-link href="/setting">⚙️&emsp;个人设置</el-link></el-option>
                <el-option label="📖&emsp;帮助手册" value="6"><el-link href="/readme">📖&emsp;帮助手册</el-link></el-option>
                <el-option label="🛠️&emsp;接口文档" value="7"><el-link href="/doc.html">🛠️&emsp;接口文档</el-link></el-option>
                <el-option label="↪️&emsp;退出登录" value="-1" @click.native="clearToken"></el-option>
            </el-select>
        </template>
    </el-page-header>
    <el-container>

        <!-- 左侧树 -->
        <el-aside class="el-aside-tree" data-scope="custom-tree">
            <!-- 右键菜单 -->
            <div v-if="contextMenu.visible && contextMenu.data.id !== -1" class="context-menu" :style="{left: contextMenu.x + 'px', top: contextMenu.y + 'px'}">
                <div v-if="contextMenu.data.id >= 0 && contextMenu.data.type === 1" class="context-menu-item" @click="openInNewTab(contextMenu.data)">
                    <i class="el-icon-position" style="margin-left: 5px"></i> 在新标签打开
                </div>
                <div v-if="contextMenu.data.id > 0 && contextMenu.data.type === 0 && contextMenu.data.isDeleted === 0" class="context-menu-item" @click="openFolderDrawer(contextMenu.data)">
                    <i class="el-icon-edit" style="margin-left: 5px"></i> 编辑文件夹
                </div>
                <div v-if="contextMenu.data.id > 0 && contextMenu.data.isDeleted === 0" class="context-menu-item" @click="deleteMemo(contextMenu.data)">
                    <i class="el-icon-delete" style="margin-left: 5px"></i> 移入回收站
                </div>
                <div v-if="contextMenu.data.id >= 0 && contextMenu.data.type === 0 && contextMenu.data.isDeleted === 0" class="context-menu-item" @click="insertMemo(contextMenu.data.id)">
                    <i class="el-icon-document-add" style="margin-left: 5px"></i> 新增文本
                </div>
                <div v-if="contextMenu.data.id >= 0 && contextMenu.data.type === 0 && contextMenu.data.isDeleted === 0" class="context-menu-item" @click="insertFolder(contextMenu.data.id)">
                    <i class="el-icon-folder-add" style="margin-left: 5px"></i> 新增文件夹
                </div>
                <div v-if="contextMenu.data.id === -99" class="context-menu-item" @click="cleanRecycle(contextMenu.data)">
                    <i class="el-icon-delete" style="margin-left: 5px"></i> 清空回收站
                </div>
                <div v-if="contextMenu.data.isDeleted === 1" class="context-menu-item" @click="cleanMemo(contextMenu.data)">
                    <i class="el-icon-delete" style="margin-left: 5px"></i>彻底删除
                </div>
                <div v-if="contextMenu.data.isDeleted === 1" class="context-menu-item" @click="recoverMemo(contextMenu.data)">
                    <i class="el-icon-refresh" style="margin-left: 5px"></i> 移出回收站
                </div>
            </div>

            <el-tree style="z-index: 999"
                     ref="memoTree"
                     :data="treeData"
                     :props="defaultProps"
                     node-key="id"
                     highlight-current
                     @node-expand="getMemoInfo"
                     @node-collapse="getMemoInfo"
                     @node-contextmenu="handleRightClick"
                     :default-expanded-keys="defaultShowNodes">
                <span class="custom-tree-node" slot-scope="{ node, data }" style="padding-top: 15px; padding-bottom: 15px;">
                    <!-- 编辑器 -->
                    <el-tooltip v-if="data.id === -1"
                                effect="dark"
                                :content="data.title"
                                placement="top-start"
                                :disabled="!isTextOverflow(data.title)"
                                popper-class="tree-node-tooltip">
                        <span @click="handleFileClick(data)" class="tree-node-text">🚥&emsp;{{ data.title }}</span>
                    </el-tooltip>

                    <!-- 记事本 -->
                    <el-tooltip v-if="data.id === 0"
                                effect="dark"
                                :content="data.title"
                                placement="top-start"
                                :disabled="!isTextOverflow(data.title)"
                                popper-class="tree-node-tooltip">
                        <span class="tree-node-text">📌&emsp;{{ data.title }}</span>
                    </el-tooltip>

                    <!-- 回收站 -->
                    <el-tooltip v-if="data.id === -99"
                                effect="dark"
                                :content="data.title"
                                placement="top-start"
                                :disabled="!isTextOverflow(data.title)"
                                popper-class="tree-node-tooltip">
                        <span class="tree-node-text">♻️&emsp;{{ data.title }}</span>
                    </el-tooltip>

                    <!-- 文件夹 -->
                    <el-tooltip v-if="data.type === 0 && data.id > 0"
                                effect="dark"
                                :content="data.title"
                                placement="top-start"
                                :disabled="!isTextOverflow(data.title)"
                                popper-class="tree-node-tooltip">
                        <span @click="handleFileClick(data)" data-scope="hide-border" class="tree-node-text">
                            <span>{{data.icon}}</span>
                            <span>{{data.title}}</span>
                        </span>
                    </el-tooltip>

                    <!-- 文本 -->
                    <el-tooltip v-if="data.type === 1"
                                effect="dark"
                                :content="data.title"
                                placement="top-start"
                                :disabled="!isTextOverflow(data.title)"
                                popper-class="tree-node-tooltip">
                        <span @click="handleFileClick(data)" data-scope="hide-border" class="tree-node-text">
                            <span v-if="data.isEditing === 0">{{ data.icon }}</span>
                            <span v-if="data.isEditing === 1">⌛️</span>
                            <span>{{data.title}}</span>
                        </span>
                    </el-tooltip>
                </span>
            </el-tree>
        </el-aside>

        <el-main>
            <!-- 代码编辑器 -->
            <div id="main" class="main" style="display: block">
                <!-- 语言 -->
                <el-select v-model="lang" size="small">
                    <el-option v-for="item in langList" :key="item.value" :label="item.value" :value="item.key" @click.native="changeLang(item.key)"/>
                </el-select>
                <!-- 主题 -->
                <el-select v-model="theme" size="small">
                    <el-option v-for="item in themeList" :key="item.value" :label="item.value" :value="item.key" @click.native="changeTheme(item.key)"/>
                </el-select>
                <!-- 字体 -->
                <el-select v-model="font" size="small">
                    <el-option v-for="item in fontList" :key="item.value" :label="item.value" :value="item.key" @click.native="changeFont(item.key)"/>
                </el-select>
                <!-- 按钮 -->
                <div style="display: inline; position: absolute; right: 5px;">
                    <el-tooltip placement="top" effect="dark">
                        <i class="el-icon-warning-outline" style="padding-right: 10px"></i>
                        <pre slot="content" style="font-family: 'Consolas'; font-size: 14px;">
1.Java: 执行代码逻辑后使用 System.out.println 输出结果

2.PHP: 执行代码逻辑后使用 echo 输出结果

3.JavaScript: 执行代码后使用 console.log 输出结果


注1: 可在个人设置中设置代码编辑器默认配置

注2: 输出存在 coder-nav-not-print，将清空打印结果

注3: 编辑器可以读取记事本目录 coder.nav 下对应编程语言代码，
     目录中的文件名为工具类名称(无需语言后缀)，结构如下

    |__ coder.nav
        |__ java
        |__ php
        |   |__ HelloWorld
        |__ javascript


示例: 如上 php 目录中存在 HelloWorld 工具类

    ```php
    &lt;?php
    class HelloWorld {
        function echo() {
            echo 'Hello world ~!';
        }
    }
    ?&gt;
    ```

编辑器中通过 HelloWorld::echo(); 即可直接引用相应代码
</pre>
                    </el-tooltip>
                    <!-- 缓存 -->
                    <el-button size="mini" type="info" plain @click="saveIdeCache" id="cache">缓存（Ctrl+S）</el-button>
                    <!-- 执行 -->
                    <el-button size="mini" type="primary" @click="run" id="run">⏩ 执行（Ctrl+Enter）</el-button>
                </div>
                <!-- 代码 -->
                <textarea id="code" name="code"></textarea>
                <!-- 输出 -->
                <h4>输出</h4>
                <textarea id="stdout" name="stdout" readonly></textarea>
                <br/><br/>
            </div>
            <!-- vditor -->
            <div id="memo" style="display: none">
                <div style="margin-bottom: 15px">
                    <!-- 保存 -->
                    <el-button size="small" type="primary" id="saveMemo" @click="saveMemo(false)">⏩ 保存（Ctrl+S）</el-button>
                    <!-- 文件夹 -->
                    <el-select v-model="currentMemo.parentId" size="small" style="width: 210px; margin-left: 15px">
                        <el-option v-for="item in folderData" :key="item.title" :label="item.title" :value="item.id"/>
                    </el-select>
                    <!-- 文件名 -->
                    <span style="margin-left: 15px">
                        <el-input size="small" v-model="currentMemo.icon" style="width: 50px;"></el-input>
                    </span>
                    <!-- 标题 -->
                    <el-input size="small" v-model="currentMemo.title" style="width: 210px;"></el-input>
                    <!-- 排序 -->
                    <span style="margin-left: 15px">🧬<span style="font-size: 12px; color: grey; margin: 3px;">排序</span></span>
                    <el-input-number size="small" v-model="currentMemo.sort" style="width: 150px;"></el-input-number>
                    <!-- 模式 -->
                    <span style="margin-left: 15px">🎹<span style="font-size: 12px; color: grey; margin: 3px;">模式</span></span>
                    <el-select v-model="currentMemo.vditorMode" size="small" style="width: 100px;">
                        <el-option v-for="item in vditorModeList" :key="item.key" :label="item.key" :value="item.value" @click.native="changeVditorMode(item.value)"/>
                    </el-select>
                    <!-- 分享id -->
                    <span style="margin-left: 15px">
                        <a target="_blank" :href="'/share/'+currentMemo.userId+'/'+currentMemo.shareId" title="点击跳转">
                            ✈️<span style="font-size: 12px; color: grey; margin: 3px;">分享</span>
                        </a>
                    </span>
                    <el-input size="small" v-model="currentMemo.shareId" style="width: 100px;" placeholder="分享id"></el-input>
                    <!-- 说明 -->
                    <el-tooltip placement="top" effect="dark">
                        <i class="el-icon-warning-outline" style="padding-left: 10px"></i>
                        <pre slot="content" style="font-family: 'Consolas'; font-size: 14px;">
⌛️ 待保存文本

🧬 排序：数值越大越靠前

🎹 渲染模式: 即时渲染(ir)/分屏预览(sv)

✈️ 分享id: 为空则不分享

⌨️ 快捷键：

    1.保存文本: Ctrl + S

    2.切换渲染模式: Ctrl + /

</pre>
                    </el-tooltip>
                </div>
                <div id="vditor"></div>
                <!-- 表单 -->
                <el-drawer :visible.sync="folderDrawer" title="📁 文件夹" :append-to-body="true" :size="'50%'">
                    <template>
                        <el-form label-width="auto" style="padding-left: 35px; padding-right: 30px">
                            <!-- 标题 -->
                            <el-form-item label="名称">
                                <el-input class="add" v-model="currentFolder.title" placeholder="标题"></el-input>
                            </el-form-item>
                            <!-- 图标 -->
                            <el-form-item label="图标">
                                <el-input class="add" v-model="currentFolder.icon" placeholder="图标"></el-input>
                            </el-form-item>
                            <!-- 排序 -->
                            <el-form-item label="排序">
                                <el-input-number class="add" v-model="currentFolder.sort" size="mini"></el-input-number>
                                <el-tooltip placement="top" effect="dark">
                                    <i class="el-icon-warning-outline" style="padding-left: 10px"></i>
                                    <div slot="content">
                                        数值越大排序越靠前<br/>
                                    </div>
                                </el-tooltip>
                            </el-form-item>
                            <!-- 保存 -->
                            <el-form-item>
                                <el-button size="mini" type="primary" @click="saveFolder()">保存</el-button>
                            </el-form-item>
                        </el-form>
                    </template>
                </el-drawer>
            </div>
        </el-main>
    </el-container>
</div>
<script th:inline="javascript">
    var code = document.getElementById("code");
    var autoSaveMemoTimer = null;
    var Main = {
        data() {
            return {
                selectedState: '1',
                // 表格
                lang: 'JavaScript', langList: [], font: '18px', fontList: [], theme: 'mdn-like', themeList: [],
                // 树
                treeData: [], folderData: [], defaultProps: {children: 'children', label: 'title',},
                // 默认展开节点
                defaultShowNodes: [],
                currentMemo: {id: null, parentId: null, sort: 0, type: 0, title: '', context: '', icon: '', vditorMode: 'ir',},
                currentFolder: {id: null, parentId: null, sort: 0, type: 0, title: '', context: '', icon: '', vditorMode: 'ir',},
                // vditor
                vditorModeList: [], vditor: null, folderDrawer: false,
                // 右键菜单
                contextMenu: {visible: false, x: 0, y: 0, data: {}}
            }
        },
        mounted:function() {
            // 检查Token
            this.checkToken()
            // 读取URL参数并打开对应文件
            this.loadFileFromUrl()
            // 获取默认配置
            axios.get('/setting/getIdeSetting', {
                headers: {
                    token: localStorage.getItem('token')
                }
            }).then((response) => {
                this.$nextTick(() => {
                    // 加载默认配置
                    codeMirrorConfig = response.data.data
                    this.changeLang(response.data.data.defaultLang)
                    this.changeFont(response.data.data.defaultFont)
                    this.changeTheme(response.data.data.defaultTheme)
                });
            });
            axios.get('/setting/getMemoSetting', {
                headers: {
                    token: localStorage.getItem('token')
                }
            }).then((response) => {
                var that = this
                this.$nextTick(() => {
                    // 自动保存
                    var memo = document.getElementById('memo');
                    var autoSaveMinute = response.data.data.autoSaveMinute
                    autoSaveMemoTimer = setInterval(function() {
                        if(memo.style.display === 'block'
                            && autoSaveMinute > 0
                            && that.$data.currentMemo.isEditing === 1) {
                            that.saveMemo(true)
                        }
                    }, autoSaveMinute * 60 * 1000)
                });
            });

            // 添加全局点击事件监听，用于隐藏右键菜单
            document.addEventListener('click', this.hideContextMenu);
        },
        beforeDestroy() {
            // 移除全局点击事件监听
            document.removeEventListener('click', this.hideContextMenu);
        },
        methods: {
            initData() {
                this.getIdeCache()
                this.getLangList()
                this.getThemeList()
                this.getFontList()
                this.getIsExpand()
                this.getTree()
                this.getFolder()
                this.getVditorModeList()
                this.initVditor('ir')
            },
            // 检查Token
            checkToken() {
                localStorage.setItem('jump', window.location.href);
                axios.get('/login/checkToken', {
                    params: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    if(!response.data.data) {
                        localStorage.setItem('token', '')
                        window.location.href = '/login'
                        return;
                    }
                    this.initData()
                });
            },
            // 返回
            goBack() {
                history.back()
            },
            // 右键菜单处理
            handleRightClick(event, data, node, treeNode) {
                // 阻止默认右键菜单
                event.preventDefault();
                event.stopPropagation();

                // 设置当前节点为选中状态
                this.$refs.memoTree.setCurrentKey(data.id);

                // 计算菜单位置，考虑窗口边界
                const menuWidth = 150; // 菜单预估宽度
                const menuHeight = 200; // 菜单预估高度

                let x = event.clientX;
                let y = event.clientY;

                // 检查右边界
                if (x + menuWidth > window.innerWidth) {
                    x = window.innerWidth - menuWidth - 10;
                }

                // 检查下边界
                if (y + menuHeight/2 > window.innerHeight) {
                    y = window.innerHeight - menuHeight/2 - 10;
                }

                // 检查左边界
                if (x < 0) {
                    x = 10;
                }

                // 检查上边界
                if (y < 0) {
                    y = 10;
                }

                // 显示右键菜单
                this.contextMenu = {
                    visible: true,
                    x: x,
                    y: y,
                    data: data
                };
            },
            // 隐藏右键菜单
            hideContextMenu() {
                this.contextMenu.visible = false;
            },
            // 检查文本是否溢出（用于控制tooltip显示）
            isTextOverflow(text) {
                // 这里可以根据实际需要设置文本长度阈值
                // 例如：文本长度超过20个字符就显示tooltip
                // return text && text.length > 8;
                return false;
            },
            // 运行代码
            print(arr, val) {
                arr.push(val)
            },
            run() {
                var stdoutVal = '';
                this.$loading()
                this.$message.info("代码运行中 ~~")
                axios.post('/ide/run', {
                    lang: this.$data.lang,
                    input: '',
                    code: code.val,
                }, {
                    headers: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    if(response.data.data === null) {
                        this.$message.error("代码运行失败，请重试")
                        this.$loading().close()
                        return;
                    }
                    stdoutVal = response.data.data.context
                    this.changeStdout(stdoutVal)
                    this.$loading().close()
                }).catch((response) => {
                    this.$message.error(response)
                    this.$loading().close()
                });
            },
            changeStdout(stdoutVal) {
                let length = stdoutVal.split('\n').length;
                if(length < 5) length = 5
                let cs = document.getElementsByClassName('CodeMirror');
                let font = this.$data.font.replace('px', '')
                cs[1].style.height = (font * 2 * length) + 'px';
                codeMirrorStdout.setValue(stdoutVal)
            },
            // 缓存代码
            saveIdeCache() {
                axios.post('/ide/saveIde', {
                    lang: this.$data.lang,
                    code: code.val,
                }, {
                    headers: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    if(response.data.code === 200) {
                        this.getIdeCache()
                        this.$message.info('缓存代码成功')
                    } else {
                        this.$message.error(response.data.msg)
                    }
                }).catch((response) => {
                    this.$message.error(response)
                });
            },
            getIdeCache() {
                this.$loading()
                axios.get('/ide/getIde', {
                    headers: {
                        token: localStorage.getItem('token')
                    },
                    params: {
                        lang: this.$data.lang
                    },
                }).then((response) => {
                    if(response.data.code === 200) {
                        code.val = response.data.data
                        init.setValue(code.val)
                        this.$data.code = code.val
                    } else {
                        this.$message.error(response.data.msg)
                    }
                    this.$loading().close()
                }).catch((response) => {
                    this.$loading().close()
                    this.$message.error(response)
                });
            },
            // 语言
            getLangList() {
                axios.get('/ide/getLangList', {
                    headers: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    this.$data.langList = response.data.data
                }).catch((response) => {
                    this.$message.error(response)
                });
            },
            // 主题
            getThemeList() {
                this.$data.themeList = [
                    {key: "material", value: "material",},
                    {key: "monokai", value: "monokai",},
                    {key: "darcula", value: "darcula",},
                    {key: "idea", value: "idea",},
                    {key: "mdn-like", value: "mdn-like",},
                    {key: "duotone-light", value: "duotone-light",},
                ];
            },
            // vditorModeList
            getVditorModeList() {
                this.$data.vditorModeList = [
                    {key: "即时渲染", value: "ir",},
                    {key: "分屏预览", value: "sv",},
                ];
            },
            // 字体
            getFontList() {
                this.$data.fontList = [
                    {key: "14px", value: "14px",},
                    {key: "16px", value: "16px",},
                    {key: "18px", value: "18px",},
                    {key: "20px", value: "20px",},
                    {key: "22px", value: "22px",},
                    {key: "24px", value: "24px",},
                ];
            },
            // 修改语言
            changeLang(lang) {
                this.$data.lang = lang
                this.initHint()
                this.getIdeCache()
            },
            // 修改主题
            changeTheme(theme) {
                init.setOption('theme', theme)
                codeMirrorStdout.setOption('theme', theme)
            },
            // 修改字体
            changeFont(font) {
                let cs = document.getElementsByClassName('CodeMirror');
                for(let i = 0; i < cs.length; i++) {
                    cs[i].style.fontSize = font;
                }
                init.setOption('theme', '')
                init.setOption('theme', this.$data.theme)
                codeMirrorStdout.setOption('theme', '')
                codeMirrorStdout.setOption('theme', this.$data.theme)
            },
            // 修改模式
            changeVditorMode(mode) {
                this.$data.currentMemo.context = this.vditor.getValue();
                this.$data.currentMemo.vditorMode = mode;
                this.vditor.destroy();
                document.removeEventListener('keydown', this.ctrlS);
                this.initVditor(mode);
            },
            // 记事本
            initVditor(mode) {
                var pageHeight = innerHeight||document.documentElement.clientHeight||document.body.clientHeight;
                var pageOffset = pageHeight * 14 / 100;
                var codeBlockPreview = true;
                // if(mode === 'ir') {
                //     codeBlockPreview = false;
                // }
                this.vditor = new Vditor('vditor', {
                    placeholder: '请输入内容...',
                    mode: mode,
                    tab: '    ',
                    height: pageHeight - pageOffset,
                    counter: {
                        enable: false,
                    },
                    outline: {
                        enable: true,
                        position: 'right',
                    },
                    preview: {
                        hljs: {
                            enable: true,
                            lineNumber: false,
                        },
                        markdown: {
                            autoSpace: true,
                            gfmAutoLink: true,
                            fixTermTypo: false,
                            listStyle: true,
                            mark: true,
                            codeBlockPreview: codeBlockPreview,
                            mathBlockPreview: codeBlockPreview,
                        },
                    },
                    typewriterMode: true,
                    after: () => {
                        this.vditor.setValue(this.$data.currentMemo.context)
                        var vditorRest = document.getElementsByClassName('vditor-reset');
                        var that = this;
                        for(var i = 0; i < vditorRest.length; i++) {
                            vditorRest[i].style.padding = '10 10';
                            vditorRest[i].addEventListener('input', function (event) {
                                that.$data.currentMemo.isEditing = 1
                                that.$refs.memoTree.setCurrentKey(that.$data.currentMemo.id)
                                that.$refs.memoTree.getCurrentNode().isEditing = 1
                            });
                            vditorRest[i].addEventListener('paste', function (event) {
                                that.$data.currentMemo.isEditing = 1
                                that.$refs.memoTree.setCurrentKey(that.$data.currentMemo.id)
                                that.$refs.memoTree.getCurrentNode().isEditing = 1
                            });
                        }
                        // 监听键盘事件
                        document.addEventListener('keydown', this.ctrlS);
                    },
                });
            },
            // 快捷键 Vditor
            ctrlS(event) {
                // 保存 Ctrl + S
                if(this.$data.currentMemo.id > 0 && (event.ctrlKey || event.metaKey) && event.key === 's') {
                    event.preventDefault();
                    this.saveMemo(false);
                }
                // 修改渲染模式 Ctrl + /
                if(this.$data.currentMemo.id > 0 && (event.ctrlKey || event.metaKey) && event.key === '/') {
                    event.preventDefault();
                    var mode = this.$data.currentMemo.vditorMode === 'ir' ? 'sv' : 'ir';
                    this.changeVditorMode(mode);
                }
            },
            // 获取树
            getTree() {
                axios.get('/memo/getTree', {
                    headers: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    this.$data.treeData = response.data.data
                    this.$nextTick(() => {
                        this.$data.currentMemo.isEditing = 0
                        this.$refs.memoTree.setCurrentKey(this.$data.currentMemo.id)
                    })
                }).catch((response) => {
                    this.$message.error(response)
                });
            },
            // 获取节点展开
            getIsExpand() {
                axios.get('/memo/getIsExpand', {
                    headers: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    this.$data.defaultShowNodes = response.data.data
                }).catch((response) => {
                    this.$message.error(response)
                });
            },
            // 目录下拉
            getFolder() {
                axios.get('/memo/getFolder', {
                    headers: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    this.$data.folderData = response.data.data
                }).catch((response) => {
                    this.$message.error(response)
                });
            },
            // 记事本-保存
            saveMemo(isAuto) {
                if(!isAuto) {
                    var that = this
                    that.$loading()
                    setTimeout(function() {that.$loading().close()}, 350)
                }
                axios.post('/memo/save', {
                    id: this.$data.currentMemo.id,
                    parentId: this.$data.currentMemo.parentId,
                    type: 1,
                    sort: this.$data.currentMemo.sort,
                    title: this.$data.currentMemo.title,
                    context: this.vditor.getValue(),
                    icon: this.$data.currentMemo.icon,
                    shareId: this.currentMemo.shareId,
                    vditorMode: this.$data.currentMemo.vditorMode,
                }, {
                    headers: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    if(response.data.data === null) {
                        // 删除定时器
                        clearInterval(autoSaveMemoTimer)
                        this.$message.error(response.data.msg)
                        return;
                    }
                    this.getTree()
                    this.getFolder()
                }).catch((response) => {
                    this.$message.error(response)
                });
            },
            // 新增记事本
            openFolderDrawer(data) {
                this.$data.folderDrawer = true
                this.$data.currentFolder = data
                this.hideContextMenu(); // 隐藏右键菜单
            },
            saveFolder() {
                axios.post('/memo/save', {
                    id: this.$data.currentFolder.id,
                    parentId: this.$data.currentFolder.parentId,
                    sort: this.$data.currentFolder.sort,
                    title: this.$data.currentFolder.title,
                    icon: this.$data.currentFolder.icon,
                    type: 0,
                    context: "",
                    vditorMode: 'ir',
                }, {
                    headers: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    this.getTree()
                    this.getFolder()
                    this.$data.folderDrawer = false;
                    this.$message.info("保存成功")
                }).catch((response) => {
                    if(response.data.data === null) {
                        this.$message.error("保存失败")
                        return;
                    }
                    this.$message.error(response)
                });
            },
            // 新增文件
            insertMemo(parentId) {
                axios.post('/memo/save', {
                    parentId: parentId,
                    type: 1,
                    sort: 0,
                    title: "New Text",
                    context: "",
                }, {
                    headers: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    if(response.data.data === null) {
                        this.$message.error("新增文本失败")
                        return;
                    }
                    this.getTree()
                    this.getFolder()
                    this.hideContextMenu(); // 隐藏右键菜单

                    var temp = {
                        id: response.data.data.id,
                        type: 1,
                    }
                    this.getMemoInfo(temp)
                }).catch((response) => {
                    this.$message.error(response)
                });
            },
            // 新增文件夹
            insertFolder(parentId) {
                this.$loading()
                axios.post('/memo/save', {
                    parentId: parentId,
                    type: 0,
                    sort: 0,
                    title: "New Folder",
                    context: "",
                }, {
                    headers: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    this.$loading().close()
                    if(response.data.data === null) {
                        this.$message.error("新增文件夹失败")
                        return;
                    }
                    this.openFolderDrawer(response.data.data)
                    this.getTree()
                    this.getFolder()
                    this.hideContextMenu()
                }).catch((response) => {
                    this.$loading().close()
                    this.$message.error(response)
                });
            },
            deleteMemo(item) {
                axios.delete('/memo/delete', {
                    headers: {
                        token: localStorage.getItem('token')
                    },
                    params: {
                        id: item.id
                    },
                }).then((response) => {
                    this.$message.info('移入回收站成功')
                    this.getTree()
                    this.getFolder()
                    this.hideContextMenu();

                }).catch((response) => {
                    this.$message.error(response)
                });
            },
            recoverMemo(item) {
                axios.delete('/memo/recover', {
                    headers: {
                        token: localStorage.getItem('token')
                    },
                    params: {
                        id: item.id
                    },
                }).then((response) => {
                    this.$message.info('移出回收站成功')
                    this.getTree()
                    this.getFolder()
                    this.hideContextMenu();
                }).catch((response) => {
                    this.$message.error(response)
                });
            },
            cleanRecycle(item) {
                this.$confirm('确认清空回收站？（不可恢复）', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    axios.delete('/memo/cleanRecycle', {
                        headers: {
                            token: localStorage.getItem('token')
                        },
                        params: {
                            id: item.id
                        },
                    }).then((response) => {
                        this.$message.info('清空回收站成功')
                        this.getTree()
                        this.getFolder()
                        this.hideContextMenu();
                    }).catch((response) => {
                        this.$message.error(response)
                    });
                }).catch((response) => {
                    this.$message.error(response)
                });
            },
            cleanMemo(item) {
                this.$confirm('确认彻底删除？（不可恢复）', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    axios.delete('/memo/cleanMemo', {
                        headers: {
                            token: localStorage.getItem('token')
                        },
                        params: {
                            id: item.id
                        },
                    }).then((response) => {
                        this.$message.info('彻底删除成功')
                        this.getTree()
                        this.getFolder()
                        this.hideContextMenu(); // 隐藏右键菜单
                        var ide = document.getElementById('main');
                        var memo = document.getElementById('memo');
                        if(item.id === this.$data.currentMemo.id) {
                            ide.style.display = 'block';
                            memo.style.display = 'none';
                            this.$data.currentMemo.id = -1;
                        }
                    }).catch((response) => {
                        this.$message.error(response)
                    });
                }).catch((response) => {
                    this.$message.error(response)
                });
            },
            // 记事本-详情
            getMemoInfo(item) {
                if(item.id === 0 || item.id === -99) {
                    return;
                }
                if (this.$data.currentMemo.isEditing === 1) {
                    this.$confirm('存在未保存的修改内容，是否保存？', '提示', {
                        confirmButtonText: '是',
                        cancelButtonText: '否',
                        type: 'warning'
                    }).then(() => {
                        this.saveMemo();
                    }).finally(() => {
                        this.getTree();
                        this.getMemoInfoAction(item);
                    });
                } else {
                    this.getMemoInfoAction(item);
                }
            },
            getMemoInfoAction(item) {
                var ide = document.getElementById('main');
                var memo = document.getElementById('memo');
                if(item.id === -1) {
                    ide.style.display = 'block';
                    memo.style.display = 'none';
                    this.$data.currentMemo.id = -1;
                    this.$refs.memoTree.setCurrentKey(-1);
                } else {
                    ide.style.display = 'none';
                    memo.style.display = 'block';
                    if(item.type === 1) {
                        this.$loading()
                    }
                    axios.get('/memo/getInfo', {
                        headers: {
                            token: localStorage.getItem('token')
                        },
                        params: {
                            id: item.id
                        }
                    }).then((response) => {
                        if(item.type === 0) {
                            // 目录展开
                            var id = item.id;
                            var isExpand = response.data.data.isExpand;
                            if(isExpand === 1) {
                                // 已经展开了，需要从数组去掉
                                this.collapseChildren(item)
                            } else {
                                // 保存当前展开的节点
                                let flag = false
                                this.$data.defaultShowNodes.some(it => {
                                    if (it === id) {
                                        flag = true
                                        return true
                                    }
                                })
                                if (!flag) {
                                    this.$data.defaultShowNodes.push(id)
                                }
                            }
                            axios.post('/memo/saveIsExpand', {
                                folderIds: this.$data.defaultShowNodes,
                            }, {
                                headers: {
                                    token: localStorage.getItem('token')
                                }
                            }).then((response) => {
                                // this.getTree()
                            })
                        } else {
                            // 文本赋值
                            this.$data.currentMemo = response.data.data;
                            this.vditor.setValue(this.$data.currentMemo.context);
                            this.changeVditorMode(this.$data.currentMemo.vditorMode);
                            this.$refs.memoTree.setCurrentKey(this.$data.currentMemo.id);
                            this.$loading().close()
                        }
                    }).catch((response) => {
                        this.$loading().close()
                        this.$message.error(response)
                    });
                }
            },
            collapseChildren(node) {
                this.$data.defaultShowNodes.some((item, i) => {
                    if(item === node.id) {
                        this.$data.defaultShowNodes.splice(i, 1)
                    }
                })
                if(node.children && node.children.length > 0) {
                    node.expanded = false;
                    node.children.forEach(child => this.collapseChildren(child));
                }
            },
            initHint() {
                axios.get('/setting/getHintSetting', {
                    headers: {
                        token: localStorage.getItem('token')
                    },
                    params: {
                        lang: this.$data.lang
                    },
                }).then((response) => {
                    codeMirrorSnippets = response.data.data;
                    initCodeMirror()
                    this.changeFont(this.$data.font)
                    this.changeTheme(this.$data.theme)
                    // 初始化输出框长度
                    let cs = document.getElementsByClassName('CodeMirror');
                    let font = this.$data.font.replace('px', '')
                    cs[1].style.height = (font * 2 * 5) + 'px';
                });
            },
            clearToken() {
                axios.get('/logout', {
                    params: {
                        token: localStorage.getItem('token')
                    }
                }).then((response) => {
                    localStorage.setItem('token', '')
                    this.checkToken()
                    this.$message.info("退出登录成功")
                })
            },
            jump(url) {
                window.location.href = url
            },
            // 点击左侧文件时更新URL参数
            handleFileClick(data) {
                if (data.type === 1) { // 只有文本文件才更新URL
                    this.updateUrlWithFileId(data.id);
                    this.getMemoInfo(data);
                } else if (data.type === 0) { // 文件夹
                    this.getMemoInfo(data);
                }
            },
            // 更新URL参数
            updateUrlWithFileId(fileId) {
                const currentUrl = new URL(window.location.href);
                currentUrl.searchParams.set('id', fileId);

                // 使用replaceState更新URL而不刷新页面
                window.history.replaceState({}, '', currentUrl.toString());
            },
            // 在新标签打开
            openInNewTab(data) {
                if (data.type === 1) { // 文本文件
                    const currentUrl = new URL(window.location.href);
                    currentUrl.searchParams.set('id', data.id);

                    // 在新标签打开
                    window.open(currentUrl.toString(), '_blank');
                }
                this.hideContextMenu(); // 隐藏右键菜单
            },
            // 从URL参数加载文件
            loadFileFromUrl() {
                const urlParams = new URLSearchParams(window.location.search);
                const fileId = urlParams.get('id');

                if (fileId) {
                    // 查找对应的文件数据
                    const findFileInTree = (nodes, id) => {
                        for (const node of nodes) {
                            if (node.id == id && node.type === 1) {
                                return node;
                            }
                            if (node.children) {
                                const found = findFileInTree(node.children, id);
                                if (found) return found;
                            }
                        }
                        return null;
                    };

                    // 等待树数据加载完成后查找文件
                    const tryFindFile = () => {
                        if (this.treeData && this.treeData.length > 0) {
                            const file = findFileInTree(this.treeData, fileId);
                            if (file) {
                                this.handleFileClick(file);
                            } else {
                                window.location.href = '/404'
                            }
                        } else {
                            setTimeout(tryFindFile, 100);
                        }
                    };
                    tryFindFile();
                }
            },
        }
    };
    var Ctor = Vue.extend(Main);
    new Ctor().$mount('#app');

    // 初始化 new CodeMirror
    var run = document.getElementById('run');
    var cache = document.getElementById('cache');
    var init = null;
    var codeMirrorStdout = null;
    var codeMirrorConfig = {};
    var codeMirrorSnippets = [];
    var codeMirrorVariables = [];
    initCodeMirror();
    function initCodeMirror() {
        if(init !== null) init.toTextArea()
        if(codeMirrorStdout !== null) codeMirrorStdout.toTextArea()
        codeMirrorConfig.hintOptions = {};
        codeMirrorConfig.hintOptions.hint = hint;
        codeMirrorConfig.hintOptions.completeSingle = false;

        // 快捷键 codeMirror
        codeMirrorConfig.extraKeys = {
            // 缓存代码
            "Ctrl-S": function() {
                cache.click();
            },
            // 运行代码
            "Ctrl-Enter": function() {
                run.click();
            },
            // 代码格式化
            "Ctrl-Alt-L": function() {
                code.val = formatCode(code.val);
                init.setValue(code.val);
            },
        };
        init = CodeMirror.fromTextArea(document.getElementById("code"), codeMirrorConfig);
        codeMirrorStdout = CodeMirror.fromTextArea(document.getElementById("stdout"), codeMirrorConfig);

        // 监听代码变化事件
        init.on("change", function(cm) {
            code.val = cm.getValue();
        });

        // 自定义代码补全后光标定位
        init.on('endCompletion', function(data) {
            if (data && data.getValue()) {
                const cursor = data.getCursor();
                const lines = data.getValue().split('\n');
                const context = lines[cursor.line].slice(0, cursor.ch);
                if(context.endsWith('()')) {
                    data.setCursor({line: cursor.line, ch: cursor.ch - 1});
                }
                if(context.endsWith('();')) {
                    data.setCursor({line: cursor.line, ch: cursor.ch - 2});
                }
            }
        });

        // 输入时自动触发提示
        let lastTrigger = 0;
        init.on("inputRead", (cm, input) => {
            if (Date.now() - lastTrigger < 200) return; // 节流
            lastTrigger = Date.now();
            // 变量识别
            codeMirrorVariables = new Set();
            var regex = /[\s+|\$?]\w+\s?=/g;
            var match;
            while((match = regex.exec(cm.getValue())) !== null) {
                var res = match[0];
                res = res.split(' ').join('');
                res = res.split('=').join('');
                codeMirrorVariables.add(res)
            }
            cm.showHint({ hint: hint });
        });
    }

    // 代码补全
    function hint(cm) {
        const cur = cm.getCursor();
        const token = cm.getTokenAt(cur);
        const word = token.string;
        const start = token.start;
        const end = token.end;
        if(word.length === 0) {
            return;
        }
        // 收集补全项
        const completions = [];
        // 添加代码片段
        codeMirrorSnippets.forEach(snippet => {
            var pattern = word.toLowerCase().split('').join('.*');
            var regexp = new RegExp(`^.*${pattern}.*$`);
            if(!regexp.test(snippet.trigger.toLowerCase())) {
                return;
            }
            completions.push({
                text: snippet.text,
                render: (element, self, data) => {
                    const div = document.createElement("div");
                    div.textContent = snippet.displayText + '  ';
                    element.appendChild(div);
                }
            });
        });
        // 变量
        codeMirrorVariables.forEach(variable => {
            var isPhpVar = false;
            if(variable.includes('$')) {
                isPhpVar = true;
                variable = variable.replace('$', '')
            }
            if (variable.toLowerCase().startsWith(word.toLowerCase())) {
                completions.unshift({
                    text: (isPhpVar ? '$' : '') + variable,
                    render: (element, self, data) => {
                        const div = document.createElement("div");
                        div.textContent = '⚡ ' + variable + '  ';
                        element.appendChild(div);
                    }
                });
            }
        })
        if (completions.length === 0) return null;
        return {
            list: completions,
            from: CodeMirror.Pos(cur.line, start),
            to: CodeMirror.Pos(cur.line, end)
        };
    }

</script>
</body>
</html>